package gspringboot

import (
	"context"
	"gitee.com/hongzhaomin/gspring"
	"gitee.com/hongzhaomin/gspring/iface"
	"gitee.com/hongzhaomin/gspring/tools"
	"gitee.com/hongzhaomin/hzm-common-go/easyconfig"
	"gitee.com/hongzhaomin/hzm-common-go/easylog"
	"gitee.com/hongzhaomin/hzm-common-go/toolkit"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"
)

func Run() {
	NewGspringApplication().Run()
}

func NewGspringApplication() *GspringApplication {
	g := &GspringApplication{
		banner:       gspringBootBanner{},
		bannerMode:   ON,
		shutdownHook: NewGspringApplicationShutdownHook(),
		wg:           new(sync.WaitGroup),
		runListeners: NewApplicationRunListeners(),
	}

	// 添加日志相关监听器和销毁处理器
	logListener := &LogListener{}
	g.AddRunListeners(logListener)
	g.AddShutdownHandler(func() {
		logListener.Shutdown()
	})
	return g
}

type GspringApplication struct {
	banner       Banner                          // 自定义banner
	bannerMode   BannerMode                      // banner模式：开启或关闭
	shutdownHook *GspringApplicationShutdownHook // 容器关闭钩子
	wg           *sync.WaitGroup                 // go协程等待组
	runListeners *ApplicationRunListeners        // 容器启动监听对象
	log          iface.GspringLogger             // 日志对象
	environment  ConfigurationEnvironment        // 环境对象
}

func (my *GspringApplication) SetBanner(banner Banner) *GspringApplication {
	my.banner = banner
	return my
}

func (my *GspringApplication) SetBannerMode(bannerMode BannerMode) *GspringApplication {
	my.bannerMode = bannerMode
	return my
}

func (my *GspringApplication) AddShutdownHandler(shutdownHandler func()) *GspringApplication {
	if shutdownHandler != nil {
		my.shutdownHook.addShowdownHandler(shutdownHandler)
	}
	return my
}

func (my *GspringApplication) AddRunListeners(listeners ...ApplicationRunListener) *GspringApplication {
	my.runListeners.AddListeners(listeners...)
	return my
}

func (my *GspringApplication) Run() {
	// 创建一个取消上下文
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// 启动1个 goroutine
	my.wg.Add(1)
	go my.runWithContext(ctx)

	// 捕获终止信号
	signalChan := make(chan os.Signal, 1)
	// syscall.SIGINT: 终端执行 Ctrl + C
	// syscall.SIGTERM: 终端执行 kill $pid
	// syscall.SIGQUIT: 终端执行 Ctrl + \
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGKILL)
	// 等待退出信号
	select {
	case <-signalChan:
		start := time.Now()
		log := my.log
		// 在退出信号到达时，先执行 cancel，发出取消信号
		if log != nil {
			log.Info("GspringBoot is shutting down")
		}
		cancel()
		// 等待 goroutine 完成退出
		my.wg.Wait()

		timeTakenToShutdown := time.Now().Sub(start)
		if timeTakenToShutdown > time.Minute {
			timeTakenToShutdown = timeTakenToShutdown.Truncate(time.Second)
		}
		if log != nil {
			log.Info("GspringBoot Shutdown in %s", timeTakenToShutdown)
		}
	}
}

func (my *GspringApplication) runWithContext(ctx context.Context) {
	startTime := time.Now()
	defer my.wg.Done()

	args := os.Args[1:]
	commandLineArgs := commandLineArgsParse(args...)
	runListeners := my.runListeners

	// prepare environment and logger
	environment := my.prepareEnvironment(commandLineArgs)
	logger := my.createLogger(environment)

	// create startup info logger
	startupInfoLog := NewStartupInfoLogger(logger, environment)
	// print banner
	my.printBanner(environment, logger)
	// print starting log
	startupInfoLog.logStarting()
	// print profiles active log
	startupInfoLog.logProfilesActive()
	runListeners.Starting(environment, logger)

	// prepare application context
	applicationContext := my.createApplicationContext(environment)
	startupInfoLog.logCreatedApplicationContext(applicationContext)
	// prepare context
	my.prepareContext(applicationContext, environment, logger)
	runListeners.ContextPrepared(applicationContext, environment, logger)

	// refreshing
	my.refresh(applicationContext)
	// after refreshed
	my.afterRefresh(applicationContext)

	// starting web server
	if webApp, ok := applicationContext.(*WebApplicationContext); ok {
		webApp.startHttpServer()
		my.AddShutdownHandler(webApp.shutdown)
	}

	// gspringboot application completed time taken
	startupInfoLog.logStarted(startTime)
	runListeners.Stared(applicationContext, environment, logger)

	// callRunners
	my.callRunners(applicationContext, commandLineArgs)
	runListeners.Ready(applicationContext, environment, logger)

	// 收到取消信号，关闭容器
	select {
	case <-ctx.Done():
		my.shutdownHook.run()
	}
}

func (my *GspringApplication) prepareEnvironment(commandLineArgs *CommandLineArgs) ConfigurationEnvironment {
	environment := newDefaultEnvironment()
	environment.addPropertiesSource(CommandLineArgsPropertiesName, NewCommandLineArgsPropertiesSource(*commandLineArgs))
	environment.addPropertiesSource(SystemEnvArgsPropertiesName, NewSystemEnvPropertiesSource())

	// 查找配置文件目录，未配置就取当前目录
	configFileDir := environment.GetStringReplacePlaceholders(gspringAppConfigFileDirPropName)

	// 第一次查找配置文件
	configFiles := my.findConfigFiles(configFileDir, environment,
		func(key string) (string, bool) {
			return environment.GetStringReplacePlaceholders(key), true
		})

	if len(configFiles) > 0 {
		properties := easyconfig.Builder().AddFiles(configFiles...).AddWatcher(propertiesWatcher).CloseLog().Build()
		// 第二次从配置文件中再次查找一遍
		configFiles = my.findConfigFiles(configFileDir, environment,
			func(key string) (string, bool) {
				if properties.ContainsKey(key) {
					return getStringReplacePlaceholders(key, properties), true
				}
				applicationConfigPropertiesSource := NewApplicationConfigPropertiesSource(properties)
				environment.addPropertiesSource(ApplicationConfigPropertiesName, applicationConfigPropertiesSource)
				return "", false
			})
	}

	// 构建app配置文件数据源
	if len(configFiles) > 0 {
		properties := easyconfig.Builder().AddFiles(configFiles...).AddWatcher(propertiesWatcher).CloseLog().Build()
		applicationConfigPropertiesSource := NewApplicationConfigPropertiesSource(properties)
		environment.addPropertiesSource(ApplicationConfigPropertiesName, applicationConfigPropertiesSource)
	}
	my.environment = environment
	return environment
}

func (my *GspringApplication) createLogger(environment ConfigurationEnvironment) iface.GspringLogger {
	logFile := environment.GetStringReplacePlaceholders(logFilePropName)
	logLevel := environment.GetStringReplacePlaceholders(logLevelPropName)
	// fixme 远程日志
	logger := NewDefaultLogger(getLogLevelEnum(logLevel).getSlogLevel(), logFile, nil)
	my.log = logger
	return logger
}

func (my *GspringApplication) createApplicationContext(environment ConfigurationEnvironment) ConfigurationApplicationContext {
	defaultAppCtxFactory := new(DefaultApplicationContextFactory)
	return defaultAppCtxFactory.Create(WebApp, environment)
}

func (my *GspringApplication) prepareContext(context ConfigurationApplicationContext,
	environment ConfigurationEnvironment, logger iface.GspringLogger) {
	context.SetEnvironment(environment)
	context.SetLogger(logger)
	my.shutdownHook.logger = logger
}

func (my *GspringApplication) printBanner(environment ConfigurationEnvironment, logger iface.GspringLogger) {
	if ON.is(my.bannerMode) {
		banner := getBanner(environment, my.banner)
		banner.PrintBanner(environment, logger)
	}
}

func (my *GspringApplication) refresh(context ConfigurationApplicationContext) {
	// 设置 gspring 默认容器
	gspring.SetDefaultCtx(context)
	my.shutdownHook.registerApplicationContext(context)

	// do refresh
	gspring.Refresh()
}

func (my *GspringApplication) afterRefresh(context ConfigurationApplicationContext) {
	logger := context.GetLogger()
	environment := context.GetEnvironment()
	appConfig := environment.GetPropertiesSource(ApplicationConfigPropertiesName)
	// 支持配置文件与配置对象属性之间的数据绑定
	if appConfig != nil {
		properties := appConfig.(easyconfig.Properties)
		configBeans := gspring.GetBeans[easyconfig.ConfigurationBean]()
		for _, configBean := range configBeans {
			err := properties.ResolveAndSetConfigBeans(configBean)
			if err != nil {
				logger.Error("Configuration properties bean [%s] resolve and binding failed: %s",
					tools.GetFullPathName(configBean), err)
			}
		}
	}
}

func (my *GspringApplication) callRunners(context ConfigurationApplicationContext, commandLineArgs *CommandLineArgs) {
	logger := context.GetLogger()
	runners := gspring.GetBeans[ApplicationRunner]()
	for _, runner := range runners {
		if err := runner.Run(*commandLineArgs); err != nil {
			logger.Error("Failed to execute ApplicationRunner: %s", err)
			panic(err)
		}
	}
}

func (my *GspringApplication) findConfigFiles(configFileDir string, environment ConfigurationEnvironment,
	getProfileStr func(key string) (string, bool)) []string {
	// 获取profiles
	activeProfileStr, exist := getProfileStr(gspringProfilesActivePropName)
	if !exist {
		return nil
	}
	activeProfiles := strings.FieldsFunc(activeProfileStr, func(r rune) bool {
		return r == ',' || r == ' ' || r == '，'
	})
	environment.AddActiveProfiles(activeProfiles...)

	// 查找配置文件
	configFiles := make([]string, 0, 4)
	for _, profile := range environment.GetActiveProfiles() {
		files := my.doFindConfigFiles(configFileDir, profile)
		if len(files) > 0 {
			configFiles = append(configFiles, files...)
		}
	}
	// 如果为空，找一遍默认配置文件
	if len(environment.GetActiveProfiles()) == 0 {
		files := my.doFindConfigFiles(configFileDir, "")
		if len(files) > 0 {
			configFiles = append(configFiles, files...)
		}
	}
	return configFiles
}

func (my *GspringApplication) doFindConfigFiles(dir, profile string) []string {
	matches, err := toolkit.FindFilesWithPrefix(dir, appConfigFilePrefix)
	if err != nil {
		easylog.Error("find files error: %v", err)
		return nil
	}
	if matches == nil {
		return nil
	}

	files := make([]string, 0)
	for _, fileName := range matches {
		name := strings.ReplaceAll(fileName, filepath.Ext(fileName), "")
		if profile == "" {
			if name == appConfigFilePrefix {
				files = append(files, filepath.Join(dir, fileName))
			}
		} else {
			if name == appConfigFilePrefix || strings.Contains(name, profile) {
				files = append(files, filepath.Join(dir, fileName))
			}
		}
	}
	return files
}
